package maple.core;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.logging.SimpleFormatter;
import java.util.Vector;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.domain.AMSService;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.AMSAgentDescription;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.Property;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.domain.FIPAException;
import jade.lang.acl.ACLMessage;

import maple.lang.Language;
import maple.util.Planner;
import maple.util.Sensor;
import maple.util.Service;

public abstract class MapleAgent extends Agent
{
	public static final String PORT = "7778";

	protected Logger _logger;

	protected int _min_arguments;
	protected String[] _arguments;

	public MapleAgent()
	{
		_logger = Logger.getLogger( getClass().getSimpleName() );
		_logger.setLevel( Level.ALL );
		_logger.setUseParentHandlers( false );

		try
		{
			FileHandler handler = new FileHandler( getLogFilename() );
			handler.setFormatter( new SimpleFormatter() );
			_logger.addHandler( handler );
		}
		catch( IOException e )
		{
			_logger.log( Level.WARNING, "", e );
			doDelete();
		}

		_min_arguments = 0;
		_arguments = null;
	}

	protected void setup()
	{

		Object[] arguments = getArguments();
		if( arguments != null )
		{
			if( arguments.length == _min_arguments )
			{
				_arguments = new String[ arguments.length ];
				for( int i = 0; i < _min_arguments; ++i )
				{
					_arguments[ i ] = arguments[ i ].toString();
				}
			}
			else
			{
				doDelete();
			}
		}

		addBehaviour( new CheckBehaviour( this ) );
		addBehaviour( new DistributeBehaviour( this ) );

		register();
		
		System.out.println( getName() + " is ready." );
	}

	protected String getLogFilename()
	{
		return "log/" + getClass().getSimpleName() + ".log";
	}

	protected void takeDown()
	{
		deregister();
	}

	/**
	 * receive the inform message
	 *
	 * @author	snowfoxy
	 * @since	2011-11-26
	 */
	protected void actionInform( final ACLMessage message )
	{
	}

	/**
	 * receive the request message
	 *
	 * @author	snowfoxy
	 * @since	2011-11-26
	 */
	protected void actionRequest( final ACLMessage message )
	{
	}

	/**
	 * check the routines
	 *
	 * @author	snowfoxy
	 * @since	2011-11-24
	 */
	protected void check()
	{
	}

	/**
	 * de-register services
	 *
	 * @author	snowfoxy
	 * @since	2011-12-18
	 */
	protected void deregister()
	{
	}

	/**
	 * return true if receiving a message
	 *
	 * @return	true if receiving a message
	 *
	 * @author	snowfoxy
	 * @since	2011-11-15
	 */
	protected boolean distribute()
	{
		ACLMessage message = receive();
		if( message != null )
		{
			if( message.getLanguage().equals( Language.class.getName() ) )
			{
				switch( message.getPerformative() )
				{
					case ACLMessage.INFORM:
						actionInform( message );
						break;
					case ACLMessage.REQUEST:
						actionRequest( message );
						break;
				}
			}
			return true;
		}
		return false;
	}

	/**
	 * return true if the specified name is existed
	 *
	 * @param	name	the specified name
	 * @return	true if the specified name is alive
	 *
	 * @author	snowfoxy
	 * @since	2011-11-15
	 */
	protected boolean exist( final String name ) 
	{
		try
		{
			String[] tokens = name.split( Language.AT );
			if( tokens.length == 2 )
			{
				AMSAgentDescription agent = new AMSAgentDescription();
				agent.setName( new AID( name, true ) );
				AID ams = new AID( "ams" + Language.AT + tokens[ 1 ], AID.ISGUID );
				ams.addAddresses( "http://" + tokens[ 1 ] + ":" + MapleAgent.PORT + "/acc" );
				AMSAgentDescription[] candidate = AMSService.search( this, ams, agent );
				if( candidate.length > 0 )
				{
					String state = candidate[ 0 ].getState();
					if( state.equals( AMSAgentDescription.ACTIVE ) || state.equals( AMSAgentDescription.WAITING ) )
					{
						return true;
					}
				}
			}
		}
		catch( FIPAException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		return false;
	}

	/**
	 * return the specified agent descriptions with some specified setting
	 *
	 * @param	names			the specified names
	 * @param	types			the specified types
	 * @param	operators		the specified operators
	 * @param	preconditions	the specified preconditions
	 * @param	postconditions	the specified postconditions
	 * @return	the specified agent descriptions with some specified setting
	 *
	 * @author	snowfoxy
	 * @since	2011-11-15
	 */
	protected DFAgentDescription getInstance( final Vector< String > names, final Vector< String > types, final Vector< String > operators, final Vector< String > preconditions, final Vector< String > postconditions )
	{
		DFAgentDescription agent = new DFAgentDescription();
		agent.setName( getAID() );
		
		for( int i = 0; i < names.size(); ++i )
		{
			ServiceDescription service = Service.getInstance( names.get( i ), types.get( i ) );
			if( operators != null && !operators.get( i ).equals( "" ) )
			{
				service.addProperties( new Property( Planner.Property.OPERATOR.toString(), operators.get( i ) ) );
			}
			if( preconditions != null && !preconditions.get( i ).equals( "" ) )
			{
				service.addProperties( new Property( Planner.Property.PRECONDITION.toString(), preconditions.get( i ) ) );
			}
			if( postconditions != null && !postconditions.get( i ).equals( "" ) )
			{
				service.addProperties( new Property( Planner.Property.POSTCONDITION.toString(), postconditions.get( i ) ) );
			}
			agent.addServices( service );
		}

		return agent;
	}

	/**
	 * return the list of agent descriptions with the specified service
	 *
	 * @param	platform	the specified platform
	 * @param	service		the specified service
	 * @return	the list of agent descriptions with the specified service
	 *
	 * @author	snowfoxy
	 * @since	2011-12-09
	 */
	protected DFAgentDescription[] listAllAgents( final String platform, final ServiceDescription service )
	{
		try
		{
			DFAgentDescription agent = new DFAgentDescription();
			agent.addServices( service );
			AID df = new AID( "df" + Language.AT + platform, AID.ISGUID );
			df.addAddresses( "http://" + platform + ":" + MapleAgent.PORT + "/acc" );
			return DFService.search( this, df, agent );
		}
		catch( FIPAException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		return null;
	}

	/**
	 * return the list of service descriptions
	 *
	 * @param	platform	the specified platform
	 * @return	the list of service descriptions
	 *
	 * @author	snowfoxy
	 * @since	2011-11-24
	 */
	protected HashMap< ServiceDescription, String > listAllServices( final String platform )
	{
		try
		{
			HashMap< ServiceDescription, String > services = new HashMap< ServiceDescription, String >();
			
			AID df = new AID( "df" + Language.AT + platform, AID.ISGUID );
			df.addAddresses( "http://" + platform + ":" + MapleAgent.PORT + "/acc" );
			
			DFAgentDescription[] agents = DFService.search( this, df, new DFAgentDescription() );
			for( DFAgentDescription agent : agents )
			{
				Iterator iterator = agent.getAllServices();
				while( iterator.hasNext() )
				{
					ServiceDescription service = ( ServiceDescription ) iterator.next();
					switch( Language.Instruction.valueOf( service.getName() ) )
					{
						case READ: case WRITE:
						case INTERPRET:
						case SUBSCRIBE:
							services.put( service, agent.getName().getName() );
							break;
					}
				}
			}
			
			return services;
		}
		catch( FIPAException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		catch( IllegalArgumentException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		return null;
	}

	/**
	 * register services
	 *
	 * @author	snowfoxy
	 * @since	2011-11-26
	 */
	protected void register()
	{
	}

	/**
	 * send the message
	 *
	 * @param	performative	the specified performative
	 * @param	receiver		the specified receiver
	 * @param	content			the specified content
	 * @param	parameters		the specified parameters
	 * @param	reply			the one to receive the reply
	 * @return	true if the message is sent
	 *
	 * @author	snowfoxy
	 * @since	2011-12-07
	 */
	protected boolean sendMessage( final int performative, final String receiver, final String content, final HashMap< String, String > parameters, final String reply )
	{
		ACLMessage message = new ACLMessage( performative );
		message.setLanguage( Language.class.getName() );
		
		// set the receivers
		int counter = 0;
		String[] tokens = receiver.split( Language.COMMA );
		for( String token : tokens )
		{
			if( exist( token ) )
			{
				String[] chips = token.split( "@" ); 
				AID aid = new AID( token, AID.ISGUID );
				aid.addAddresses( "http://" + chips[ 1 ] + ":" + MapleAgent.PORT + "/acc" );
				message.addReceiver( aid );
				++counter;
			}
		}
		if( counter == 0 )
		{
			return false;
		}

		if( reply != null )
		{
			message.setReplyWith( reply );
		}

		message.setContent( content );

		// set the parameters
		if( parameters != null )
		{
			for( String parameter : parameters.keySet() )
			{
				message.addUserDefinedParameter( parameter, parameters.get( parameter ) );
			}
		}

		send( message );

		return true;
	}

	protected class CheckBehaviour extends CyclicBehaviour
	{
		public CheckBehaviour( Agent a )
		{
			super( a );
		}

		public void action()
		{
			MapleAgent agent = ( MapleAgent ) myAgent;
			agent.check();
		}
	}

	protected class DistributeBehaviour extends CyclicBehaviour
	{
		public DistributeBehaviour( Agent a )
		{
			super( a );
		}

		public void action()
		{
			MapleAgent agent = ( MapleAgent ) myAgent;
			if( !agent.distribute() )
			{
				block();
			}
		}
	}
}
